package edu.drexel.cs544.group11.client.protocol;

import java.io.IOException;
import java.net.ConnectException;
import java.util.Date;

import edu.drexel.cs544.group11.common.IllegalBSPStateException;
import edu.drexel.cs544.group11.common.MessageFormatException;
import edu.drexel.cs544.group11.common.ProtocolState;
import edu.drexel.cs544.group11.common.RequestCommand;
import edu.drexel.cs544.group11.common.RequestMessage;
import edu.drexel.cs544.group11.common.ResponseMessage;
import edu.drexel.cs544.group11.common.ResponseStatusCode;

/**
 * STATEFUL
 * 
 * CLIENT
 * 
 * An implementation of the client Barcode Scanner Protocol (BSP). This provides
 * an API so that any program can use the client portion of the BSP.
 * 
 * 
 * CS 544
 * 
 * @author Group 11:
 * @author Jason Setzer
 * @author Tam Thongsima
 * @author Kenny Oyefara
 * @author Chase Miller
 * 
 * @date Spring 2013
 * 
 */
public class ClientBSP {
    
    private ServerConnection serverConnection;
    private ProtocolState protocolState; // STATEFUL
    private static double version = 1.0;
    
    /**
     * CLIENT
     * 
     * Creates a new instance.
     * 
     * @param serverAddress - the address of the server.
     */
    public ClientBSP(String keyStorePath, String serverAddress) {
	this.serverConnection = new ServerConnection(keyStorePath, serverAddress);
	this.protocolState = protocolState.OFFLINE; // STATEFUL
    }
    
    /**
     * CLIENT
     * 
     * Creates a new instance using the autodiscover feature to find the server.
     */
    public ClientBSP(String keyStorePath) {
	this.serverConnection = new ServerConnection(keyStorePath);
	this.protocolState = protocolState.OFFLINE; // STATEFUL
    }
    
    /**
     * STATEFUL
     * 
     * Connects to the BSP server. The server address used during instantiation
     * of this object will be used.
     * 
     * @throws Exception
     */
    public void connect() throws ConnectException, IllegalBSPStateException {
	if (this.protocolState == ProtocolState.OFFLINE) {
	    this.serverConnection.establishConnection();
	    this.protocolState = ProtocolState.SECURE_READY;
	}
	else {
	    throw new IllegalBSPStateException("Could not connect to server because the protocol is not in an offline state.");
	}
    }
    
    /**
     * STATEFUL
     * 
     * Attempts to transition into an admin state.
     * 
     * @param employeeId - the employee's ID.
     * 
     * @return - True if the transition was successful.
     */
    public boolean tryTransitionToAdminSession(String employeeId) {
	boolean success = false;
	
	boolean isValidState = false;
	
	// STATEFUL
	switch (this.protocolState) {
	    case SECURE_READY:
		isValidState = true;
		break;
	    default:
		break;
	}
	
	if (isValidState) {
	    try {
		RequestMessage request = new RequestMessage(RequestCommand.TO_ADMIN_SESSION, this.serverConnection.getSeqId(), version, employeeId);
		this.serverConnection.sendRequest(request);
		ResponseMessage response = this.serverConnection.waitForResponse();
		if (response.getStatusCode() == ResponseStatusCode.SUCCESS) {
		    this.protocolState = ProtocolState.ADMIN_SESSION;
		    success = true;
		}
	    }
	    catch (IOException e1) {
	    }
	}
	
	return success;
    }
    
    /**
     * STATEFUL
     * 
     * Attempts to transition into a price check state.
     * 
     * @return - True if the transition was successful.
     */
    public boolean tryTransitionToPriceCheck() {
	boolean success = false;
	
	boolean isValidState = false;
	
	// STATEFUL
	switch (this.protocolState) {
	    case SECURE_READY:
		isValidState = true;
		break;
	    default:
		break;
	}
	
	if (isValidState) {
	    
	    try {
		RequestMessage request = new RequestMessage(RequestCommand.TO_PRICE_CHECK_SESSION, this.serverConnection.getSeqId(), version);
		this.serverConnection.sendRequest(request);
		ResponseMessage response = this.serverConnection.waitForResponse();
		if (response.getStatusCode() == ResponseStatusCode.SUCCESS) {
		    this.protocolState = ProtocolState.PRICE_CHECK_SESSION;
		    success = true;
		}
	    }
	    catch (IOException e1) {
	    }
	}
	
	return success;
    }
    
    /**
     * STATEFUL
     * 
     * Attempts to transition into the customer session.
     * 
     * @return - True if the transition was successful.
     */
    public boolean tryTransitionToCustomerSession() {
	boolean success = false;
	
	boolean isValidState = false;
	
	// STATEFUL
	switch (this.protocolState) {
	    case SECURE_READY:
		isValidState = true;
		break;
	    case CART_SCAN:
		isValidState = true;
		break;
	    case PURCHASING:
		isValidState = true;
		break;
	    case CUSTOMER_SESSION:
		return true; // we're already in this state, so no need to transition...return true.
	    default:
		break;
	}
	
	if (isValidState) {
	    
	    try {
		RequestMessage request = new RequestMessage(RequestCommand.TO_CUSTOMER_SESSION, this.serverConnection.getSeqId(), version);
		this.serverConnection.sendRequest(request);
		ResponseMessage response = this.serverConnection.waitForResponse();
		if (response.getStatusCode() == ResponseStatusCode.SUCCESS) {
		    this.protocolState = ProtocolState.CUSTOMER_SESSION;
		    success = true;
		}
	    }
	    catch (IOException e1) {
	    }
	}
	
	return success;
    }
    
    /**
     * STATEFUL
     * 
     * Attempts to transition into secure ready.
     * 
     * @return - True if the transition was successful.
     */
    public boolean tryTransitionToSecureReady() {
	boolean success = false;
	
	boolean isValidState = false;
	
	// STATEFUL
	switch (this.protocolState) {
	    case PRICE_CHECK_SESSION:
		isValidState = true;
		break;
	    case CUSTOMER_SESSION:
		isValidState = true;
		break;
	    case ADMIN_SESSION:
		isValidState = true;
		break;
	    case PURCHASING:
		isValidState = true;
		break;
	    default:
		break;
	}
	
	if (isValidState) {
	    try {
		RequestMessage request = new RequestMessage(RequestCommand.TO_SECURE_READY, this.serverConnection.getSeqId(), version);
		this.serverConnection.sendRequest(request);
		ResponseMessage response = this.serverConnection.waitForResponse();
		if (response.getStatusCode() == ResponseStatusCode.SUCCESS) {
		    this.protocolState = ProtocolState.SECURE_READY;
		    success = true;
		}
	    }
	    catch (IOException e1) {
	    }
	}
	
	return success;
    }
    
    /**
     * STATEFUL
     * 
     * Attempts to transition to the purchasing state.
     * 
     * @return - Returns true if the transition was successful.
     */
    public boolean tryTransitionToPurchasing() {
	boolean success = false;
	
	boolean isValidState = false;
	
	// STATEFUL
	switch (this.protocolState) {
	    case CUSTOMER_SESSION:
		isValidState = true;
		break;
	    default:
		break;
	}
	
	if (isValidState) {
	    try {
		RequestMessage request = new RequestMessage(RequestCommand.TO_PURCHASING, this.serverConnection.getSeqId(), version);
		this.serverConnection.sendRequest(request);
		ResponseMessage response = this.serverConnection.waitForResponse();
		if (response.getStatusCode() == ResponseStatusCode.SUCCESS) {
		    this.protocolState = ProtocolState.PURCHASING;
		    success = true;
		}
	    }
	    catch (IOException e1) {
	    }
	}
	
	return success;
    }
    
    /**
     * STATEFUL
     * 
     * Attempts to transition to the cart scan state.
     * 
     * @return - Returns true if the transition was successful.
     */
    public boolean tryTransitionToCartScan() {
	boolean success = false;
	
	boolean isValidState = false;
	
	// STATEFUL
	switch (this.protocolState) {
	    case CUSTOMER_SESSION:
		isValidState = true;
		break;
	    default:
		break;
	}
	
	if (isValidState) {
	    try {
		RequestMessage request = new RequestMessage(RequestCommand.TO_CART_SCAN, this.serverConnection.getSeqId(), version);
		this.serverConnection.sendRequest(request);
		ResponseMessage response = this.serverConnection.waitForResponse();
		if (response.getStatusCode() == ResponseStatusCode.SUCCESS) {
		    this.protocolState = ProtocolState.CART_SCAN;
		    success = true;
		}
	    }
	    catch (IOException e1) {
	    }
	}
	
	return success;
    }
    
    /**
     * STATEFUL
     * 
     * Sends a barcode to the server to get info back.
     * 
     * @throws MessageFormatException
     * @throws IOException
     * 
     * @return - Returns the info as a string.
     */
    public String scanForItemInfo(String barcode) throws IOException, IllegalBSPStateException {
	boolean isValidState = false;
	
	// STATEFUL
	switch (this.protocolState) {
	    case PRICE_CHECK_SESSION:
		isValidState = true;
		break;
	    case CART_SCAN:
		isValidState = true;
		break;
	    case ADMIN_SESSION:
		isValidState = true;
		break;
	    case CUSTOMER_SESSION:
		if (this.tryTransitionToCartScan()) {
		    isValidState = true;
		}
	    default:
		break;
	}
	
	if (isValidState) {
	    String infoToReturn = "";
	    
	    String message = barcode;
	    RequestMessage request = new RequestMessage(RequestCommand.SCAN_FOR_ITEM_INFO, this.serverConnection.getSeqId(), version, message);
	    this.serverConnection.sendRequest(request);
	    ResponseMessage response = this.serverConnection.waitForResponse();
	    if (response.getStatusCode() == ResponseStatusCode.SUCCESS) {
		infoToReturn = response.getMessageBody();
	    }
	    else {
		throw new Error("Info could not be retrieved.");
	    }
	    
	    return infoToReturn;
	}
	else {
	    throw new IllegalBSPStateException("Not in a valid state to scan an item.");
	}
    }
    
    /**
     * STATEFUL
     * 
     * Attempts to update the specified item
     * 
     * @param barcode - the barcode of the item to update.
     * @param typeOfInfo - the type of info.
     * @param infoValue - value for the supplied type of info.
     * 
     * @return True if the update was successful, otherwise false.
     */
    public boolean tryUpdateItem(String barcode, String typeOfInfo, String infoValue) throws IllegalBSPStateException {
	boolean success = false;
	
	// STATEFUL
	boolean isValidState = false;
	switch (this.protocolState) {
	    case ADMIN_SESSION:
		isValidState = true;
	    default:
		break;
	}
	
	if (isValidState) {
	    String message = "Barcode:" + barcode + ", Element:" + typeOfInfo + ", Value:" + infoValue;
	    RequestMessage request = new RequestMessage(RequestCommand.UPDATE_INVENTORY_ITEM, this.serverConnection.getSeqId(), version, message);
	    try {
		request.sendMessage(this.serverConnection.getOutToServer());
		ResponseMessage response = this.serverConnection.waitForResponse();
		if (response.getStatusCode() == ResponseStatusCode.SUCCESS) {
		    success = true;
		}
	    }
	    catch (IOException | MessageFormatException e) {
	    }
	}
	else {
	    throw new IllegalBSPStateException("Cannot update an item because protocol is not in the correct state");
	}
	
	return success;
    }
    
    /**
     * STATEFUL
     * 
     * Attempts to add an inventory item.
     * 
     * @param barcode
     * @param category
     * @param name
     * @param price
     * @param quantity
     * @return - True if the add was successful.
     * @throws IllegalBSPStateException
     */
    public boolean tryAddInventoryItem(String barcode, String category, String name, String price, String quantity) throws IllegalBSPStateException {
	boolean success = false;
	
	boolean isValidState = false;
	
	// STATEFUL
	if (this.protocolState == ProtocolState.ADMIN_SESSION) {
	    isValidState = true;
	}
	
	if (isValidState) {
	    String messageBody = "Barcode:" + barcode.replace(",", "") + ", Category:" + category.replace(",", "") + ", Name:" + name.replace(",", "") + ", Price:" + price.replace(",", "") + ", Quantity:" + quantity.replace(",", "");
	    RequestMessage request = new RequestMessage(RequestCommand.ADD_INVENTORY_ITEM, this.serverConnection.getSeqId(), version, messageBody);
	    try {
		request.sendMessage(this.serverConnection.getOutToServer());
		ResponseMessage response = this.serverConnection.waitForResponse();
		if (response.getStatusCode() == ResponseStatusCode.SUCCESS) {
		    success = true;
		}
	    }
	    catch (IOException | MessageFormatException e) {
	    }
	}
	else {
	    throw new IllegalBSPStateException("Not in a valid state to add an item to inventory.");
	}
	
	return success;
    }
    
    /**
     * STATEFUL
     * 
     * Attempts to add an item to the cart.
     * 
     * @return
     */
    public boolean tryAddItem(String barcode) throws IllegalBSPStateException {
	boolean success = false;
	
	boolean isValidState = false;
	
	// STATEFUL
	if (this.protocolState == ProtocolState.CART_SCAN) {
	    isValidState = true;
	}
	else if (this.protocolState == ProtocolState.CUSTOMER_SESSION) {
	    if (this.tryTransitionToCartScan()) {
		isValidState = true;
	    }
	}
	
	if (isValidState) {
	    String messageBody = barcode;
	    RequestMessage request = new RequestMessage(RequestCommand.ADD_ITEM, this.serverConnection.getSeqId(), version, messageBody);
	    try {
		request.sendMessage(this.serverConnection.getOutToServer());
		ResponseMessage response = this.serverConnection.waitForResponse();
		if (response.getStatusCode() == ResponseStatusCode.SUCCESS) {
		    success = true;
		}
	    }
	    catch (IOException | MessageFormatException e) {
	    }
	}
	else {
	    throw new IllegalBSPStateException("Not in a valid state to add an item to the cart.");
	}
	
	return success;
    }
    
    /**
     * STATEFUL
     * 
     * Attempts to remove an item from the cart.
     * 
     * @return
     */
    public boolean tryRemoveItem(String barcode) throws IllegalBSPStateException {
	boolean success = false;
	
	boolean isValidState = false;
	
	// STATEFUL
	if (this.protocolState == ProtocolState.CART_SCAN) {
	    isValidState = true;
	}
	else if (this.protocolState == ProtocolState.CUSTOMER_SESSION) {
	    if (this.tryTransitionToCartScan()) {
		isValidState = true;
	    }
	}
	
	if (isValidState) {
	    String messageBody = barcode;
	    RequestMessage request = new RequestMessage(RequestCommand.REMOVE_ITEM, this.serverConnection.getSeqId(), version, messageBody);
	    try {
		request.sendMessage(this.serverConnection.getOutToServer());
		ResponseMessage response = this.serverConnection.waitForResponse();
		if (response.getStatusCode() == ResponseStatusCode.SUCCESS) {
		    success = true;
		}
	    }
	    catch (IOException | MessageFormatException e) {
	    }
	}
	else {
	    throw new IllegalBSPStateException("Not in a valid state to remove an item from the cart.");
	}
	
	return success;
    }
    
    /**
     * STATEFUL
     * 
     * Attempts to pay for the items in a cart.
     * 
     * @param creditCardNumber
     * @param name
     * @param vendor
     * @param expirationDate
     * @return - Returns true if successful.
     * @throws IllegalBSPStateException
     */
    public boolean tryPay(String creditCardNumber, String name, String vendor, Date expirationDate) throws IllegalBSPStateException {
	boolean success = false;
	
	boolean isValidState = false;
	
	// STATEFUL
	if (this.protocolState == ProtocolState.PURCHASING) {
	    isValidState = true;
	}
	
	if (isValidState) {
	    String messageBody = "number:" + creditCardNumber + ", name:" + name + ", vendor:" + vendor + ", exp:" + expirationDate.getMonth() + "/" + expirationDate.getYear();
	    RequestMessage request = new RequestMessage(RequestCommand.PAY, this.serverConnection.getSeqId(), version, messageBody);
	    try {
		request.sendMessage(this.serverConnection.getOutToServer());
		ResponseMessage response = this.serverConnection.waitForResponse();
		if (response.getStatusCode() == ResponseStatusCode.SUCCESS) {
		    this.protocolState = ProtocolState.SECURE_READY;
		    success = true;
		}
	    }
	    catch (IOException | MessageFormatException e) {
	    }
	}
	else {
	    throw new IllegalBSPStateException("Not in a valid state to remove an item from the cart.");
	}
	
	return success;
    }
    
    /**
     * STATEFUL
     * 
     * Close the connection to the server and end the session.
     */
    public void endSession() throws IllegalBSPStateException {
	if (!(this.protocolState == ProtocolState.OFFLINE)) {
	    try {
		this.serverConnection.close();
	    }
	    catch (IOException e) {
	    }
	}
	else {
	    throw new IllegalBSPStateException("Cannot end session because the protocol is already in an offline state.");
	}
    }
}
